﻿using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web;

namespace AjaxControlToolkit.LongOperations
{
    internal sealed class LongOperationResultManager: IOperationResultSynchronizer
    {
        private static readonly string IdSeperator = "~!~";

        private LongOperationResultManager()
        {
        }

        public static LongOperationResultManager Instance
        {
            get
            {
                // Return our singleton instance via our nested class.
                return InstanceInitializer.instance;
            }
        }

        public static string GetHomePath(string realationFilePath)
        {
            return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, realationFilePath);
        }

        private void StoreDataToIsolatedStorage(string sessionID, string processGUID, UpdatePanelExtenderEventArgs e)
        {
            if (HttpContext.Current != null)
            {
                HttpContext.Current.SkipAuthorization = true;
            }
            string fileName = Path.ChangeExtension(GetFullProcessGUID(sessionID, processGUID), ".xml");
            if (!Directory.Exists(GetHomePath("LongOperationsStorage")))
            {
                Directory.CreateDirectory(GetHomePath("LongOperationsStorage"));
            }
            lock (Instance)
            {
                string fileFullPath = Path.Combine(GetHomePath("LongOperationsStorage\\"), fileName);
                if (File.Exists(fileFullPath))
                {
                    File.Delete(fileFullPath);
                }
                using (var fs = new FileStream(fileFullPath, FileMode.CreateNew, FileAccess.Write))
                {
                    try
                    {
                        var binaryFormatter = new BinaryFormatter();
                        binaryFormatter.Serialize(fs, e);
                        fs.Flush();
                    }
                    finally
                    {
                        fs.Close();
                    }
                }
            }
        }

        private UpdatePanelExtenderEventArgs GetDataFromIsolatedStorage(string sessionID, string processGUID)
        {
            if (HttpContext.Current != null)
            {
                HttpContext.Current.SkipAuthorization = true;
            }
            UpdatePanelExtenderEventArgs result = null;
            string fileName = Path.ChangeExtension(GetFullProcessGUID(sessionID, processGUID), ".xml");
            string fileFullPath = Path.Combine(GetHomePath("LongOperationsStorage\\"), fileName);
            lock(Instance)
            {
                if (File.Exists(fileFullPath))
                {
                    using (var fs = new FileStream(fileFullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
                    {
                        try
                        {
                            var binaryFormatter = new BinaryFormatter();
                            result = binaryFormatter.Deserialize(fs) as UpdatePanelExtenderEventArgs;
                        }
                        finally
                        {
                            fs.Close();
                        }
                    }
                    if (File.Exists(fileFullPath))
                    {
                        File.Delete(fileFullPath);
                    }
                }
            }
            return result;
        }

        private void ClearDataFromIsolatedStorage(string sessionID)
        {
            if (HttpContext.Current != null)
            {
                HttpContext.Current.SkipAuthorization = true;
            }
            lock (Instance)
            {
                string directoryPath = Path.GetFullPath(GetHomePath("LongOperationsStorage\\"));
                string[] files = Directory.GetFiles(directoryPath, Path.ChangeExtension( GetFullProcessGUID(sessionID, "*"), ".xml"));
                if ((files != null) && (files.Length > 0))
                {
                    for (int i = 0; i < files.Length; i++)
                    {
                        try
                        {
                            File.Delete(Path.Combine(directoryPath, files[i]));
                        }
                        catch (Exception)
                        {
                        }
                    }
                }
            }
        }

        private static HttpContext GetCurrentContext()
        {
            if (HttpContext.Current != null && HttpContext.Current.Session != null)
            {
                return HttpContext.Current;
            }
            return null;
        }

        private static string GetFullProcessGUID(string sessionID, string processGUID)
        {
            return sessionID + IdSeperator + processGUID;
        }

        public UpdatePanelExtenderEventArgs GetOperationState(string sessionID, string processGUID)
        {
            UpdatePanelExtenderEventArgs result;
            HttpContext context = GetCurrentContext();
            result = GetDataFromIsolatedStorage(sessionID, processGUID);
            if (result != null)
            {
                if (context != null)
                {
                    lock (Instance)
                    {
                        context.Session[GetFullProcessGUID(sessionID, processGUID)] = result;
                    }
                }
            }
            else
            {
                if (context != null)
                {
                    string sessionKey = GetFullProcessGUID(sessionID, processGUID);
                    lock (Instance)
                    {
                        if (context.Session[sessionKey] != null)
                        {
                            result = context.Session[sessionKey] as UpdatePanelExtenderEventArgs;
                            if (result == null)
                            {
                                throw new Exception("The object stored in session is not UpdatePanelExtenderEventArgs");
                            }
                        }
                    }
                }
            }
            if (result == null)
            {
                result = new UpdatePanelExtenderEventArgs("No result found", DateTime.Now, ExecutionResultState.NotFound);
            }
            return result;
        }

        public void SetOperationState(string sessionID, string processGUID, UpdatePanelExtenderEventArgs e)
        {
            HttpContext context;
            if ((context = GetCurrentContext()) != null)
            {
                lock (Instance)
                {
                    sessionID = context.Session.SessionID;
                    context.Session[GetFullProcessGUID(sessionID, processGUID)] = e;
                }
            }
            else
            {
                StoreDataToIsolatedStorage(sessionID, processGUID, e);
            }
        }

        public void ClearOperationStates(string sessionID)
        {
            HttpContext context;
            if ((context = GetCurrentContext()) != null)
            {
                lock (Instance)
                {
                    sessionID = context.Session.SessionID;
                    for (int i = 0; i < context.Session.Keys.Count; i++)
                    {
                        string key = context.Session.Keys[i];
                        if (key.StartsWith(sessionID + IdSeperator))
                        {
                            context.Session.Remove(key);
                            i--;
                        }
                    }
                }
            }
            ClearDataFromIsolatedStorage(sessionID);
        }

        #region Nested type: InstanceInitializer

        private class InstanceInitializer
        {
            // C# compiler needs static constructor so not to mark our class as beforefieldinit
            // beforefieldinit would allow our type initializer to be invoked at any time
            // before the first reference to a static field in it, this would prevent us for
            // using this method to create a fully lazy singleton. Ensuring we always do not
            // have our class marked as beforefieldinit means we are guaranteed that our type
            // initializer is only invoked when we are called and our internal static readonly
            // guarantees that we will only initialized once giving us a fully thread safe
            // fully lazy singleton.

            // Internal static readonly singleton containing our reference to our main object
            internal static readonly LongOperationResultManager instance = new LongOperationResultManager();
        }

        #endregion
    }
}